package org.andengine.opengl.texture.compressed.pvr.pixelbufferstrategy;

import android.opengl.GLES20;

import org.andengine.opengl.texture.PixelFormat;
import org.andengine.opengl.texture.compressed.pvr.PVRTexture;
import org.andengine.opengl.texture.compressed.pvr.PVRTexture.PVRTextureHeader;
import org.andengine.util.StreamUtils;
import org.andengine.util.exception.AndEngineRuntimeException;

import java.io.IOException;
import java.io.InputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;

/**
 * (c) Zynga 2011
 *
 * @author Nicolas Gramlich <ngramlich@zynga.com>
 * @since 11:26:07 - 27.09.2011
 */
public class SmartPVRTexturePixelBufferStrategy implements IPVRTexturePixelBufferStrategy {
    // ===========================================================
    // Constants
    // ===========================================================

    // ===========================================================
    // Fields
    // ===========================================================

    private final int mAllocationSizeMaximum;

    // ===========================================================
    // Constructors
    // ===========================================================

    public SmartPVRTexturePixelBufferStrategy(final int pAllocationSizeMaximum) {
        this.mAllocationSizeMaximum = pAllocationSizeMaximum;
    }

    // ===========================================================
    // Getter & Setter
    // ===========================================================

    // ===========================================================
    // Methods for/from SuperClass/Interfaces
    // ===========================================================

    @Override
    public IPVRTexturePixelBufferStrategyBufferManager newPVRTexturePixelBufferStrategyManager(final PVRTexture pPVRTexture) throws IOException {
        return new SmartPVRTexturePixelBufferStrategyBufferManager(pPVRTexture);
    }

    @Override
    public void loadPVRTextureData(final IPVRTexturePixelBufferStrategyBufferManager pPVRTexturePixelBufferStrategyManager, final int pWidth, final int pHeight, final int pBytesPerPixel, final PixelFormat pPixelFormat, final int pLevel, final int pCurrentPixelDataOffset, final int pCurrentPixelDataSize) throws IOException {
        final int glFormat = pPixelFormat.getGLFormat();
        final int glType = pPixelFormat.getGLType();

		/* Create the texture with the required parameters but without data. */
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, pLevel, pPixelFormat.getGLInternalFormat(), pWidth, pHeight, 0, glFormat, glType, null);

        final int bytesPerRow = pWidth * pBytesPerPixel;
        final int stripeHeight = Math.max(1, this.mAllocationSizeMaximum / bytesPerRow);

		/* Load stripes. */
        int currentStripePixelDataOffset = pCurrentPixelDataOffset;
        int currentStripeOffsetY = 0;
        while (currentStripeOffsetY < pHeight) {
            final int currentStripeHeight = Math.min(pHeight - currentStripeOffsetY, stripeHeight);
            final int currentStripePixelDataSize = currentStripeHeight * bytesPerRow;

			/* Adjust buffer. */
            final Buffer pixelBuffer = pPVRTexturePixelBufferStrategyManager.getPixelBuffer(PVRTextureHeader.SIZE + currentStripePixelDataOffset, currentStripePixelDataSize);

			/* Send to hardware. */
            GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, pLevel, 0, currentStripeOffsetY, pWidth, currentStripeHeight, glFormat, glType, pixelBuffer);

            currentStripePixelDataOffset += currentStripePixelDataSize;
            currentStripeOffsetY += currentStripeHeight;
        }
    }

    // ===========================================================
    // Methods
    // ===========================================================

    // ===========================================================
    // Inner and Anonymous Classes
    // ===========================================================

    public static class SmartPVRTexturePixelBufferStrategyBufferManager implements IPVRTexturePixelBufferStrategyBufferManager {
        // ===========================================================
        // Constants
        // ===========================================================

        // ===========================================================
        // Fields
        // ===========================================================

        private final InputStream mInputStream;
        private int mInputStreamPosition;

        private byte[] mData;

        // ===========================================================
        // Constructors
        // ===========================================================

        public SmartPVRTexturePixelBufferStrategyBufferManager(final PVRTexture pPVRTexture) throws IOException {
            this.mInputStream = pPVRTexture.getInputStream();
        }

        // ===========================================================
        // Getter & Setter
        // ===========================================================

        // ===========================================================
        // Methods for/from SuperClass/Interfaces
        // ===========================================================

        @Override
        public ByteBuffer getPixelBuffer(final int pStart, final int pByteCount) throws IOException {
            if (pStart < this.mInputStreamPosition) {
                throw new AndEngineRuntimeException("Cannot read data that has been read already. (pStart: '" + pStart + "', this.mInputStreamPosition: '" + this.mInputStreamPosition + "')");
            }

			/* Ensure data buffer is bug enough. */
            if (this.mData == null || this.mData.length < pByteCount) {
                this.mData = new byte[pByteCount];
            }

			/* If needed, skip bytes up to where the data was requested. */
            if (this.mInputStreamPosition < pStart) {
                final int bytesToSkip = pStart - this.mInputStreamPosition;
                final long skipped = this.mInputStream.skip(bytesToSkip);

                this.mInputStreamPosition += skipped;

                if (bytesToSkip != skipped) {
                    throw new AndEngineRuntimeException("Skipped: '" + skipped + "' instead of '" + bytesToSkip + "'.");
                }
            }

			/* Read the requested data. */
            final int bytesToRead = pStart + pByteCount - this.mInputStreamPosition;
            StreamUtils.streamToBytes(this.mInputStream, bytesToRead, this.mData);
            this.mInputStreamPosition += bytesToRead;

			/* Return as a buffer. */
            return ByteBuffer.wrap(this.mData, 0, pByteCount);
        }

        // ===========================================================
        // Methods
        // ===========================================================

        // ===========================================================
        // Inner and Anonymous Classes
        // ===========================================================
    }
}